1 影音元数据

1)首先介绍一个影音元数据库的网站TMDB。

https://www.themoviedb.org/

注册账号并获取api的key

2)字幕网站

https://www.opensubtitles.com/

注册账号并获取api的key

3)关于刮削的文件命名

基本信息:

原剧集语言
固定演员

参考:

https://www.bilibili.com/read/cv3275648/

https://www.luxiyue.com/personal/%E4%B8%80%E6%96%87%E6%90%9E%E6%87%82%E7%94%B5%E5%BD%B1%E6%96%87%E4%BB%B6%E5%91%BD%E5%90%8D%E8%A7%84%E5%88%99/

https://blog.iplayloli.com/sharing-of-tinymeediamanger-renaming-rules.html

8.4 影视刮削工具

下载刮削工具 TinyMediaManager

下载地址:https://www.tinymediamanager.org/

批量重命名工具 Advance Renamer

下载地址:https://www.advancedrenamer.com/

assets/nas017.png

assets/nas019.png

8.2 影音命名规则

影音分为两种。
1)MOVIE:电影
2)TVSHOW:剧集(例如连续剧或者番剧)

8.2.1电影命名规则

文件夹命名为
影片名称(年份)

电影命名:
电影中文名.电影原始名.发行年份.分辨率.影片来源.影片编码.影片格式
加勒比海盗2:聚魂棺.Pirates.of.the.Caribbean.Dead.Mans.Chest.2006.BluRay.720p.x264.AC3-WOFEI.mkv

8.2.1剧集命名规则

Friends.S03E10.1080p.x264.mkv
节目名称.第几季第几集.分辨率.影片编码.视频格式

8.3 字幕编辑

subtitleEdit 字幕编辑

.default.字母格式: 在文件名末尾附加字幕来将字幕标记为默认字幕

8.4 刮削后整理:重命名

8.4.1 电影
文件夹重命名配置:

${title}${ - ,edition,} (${,year,})

文件命名配置:
${title}${if !movie.title=movie.originalTitle}${.originalTitle}${end}${.,year,}${.,edition,}${.,mediaSource,}${.,videoFormat,}${.,videoCodec,}.${videoBitDepth}bit.${.,audioCodec;upper,}

assets/nas020.png

8.4.2电视

1)文件夹重命名配置:
${showTitle} (${showYear})

2)特别篇或者OVA目录
文件命名配置:
Specials

3)季命名
Season ${seasonNr}

3)文件命名
${tvShow.title}${if !tvShow.title=tvShow.originalTitle}${.,tvShow.originalTitle,}${end}.S${seasonNr2}E${episodeNr2}${.,videoFormat,;replace(format.csv)}${.,videoCodec,;replace(codec.csv)}

第几季第几集用S季数E集数表示
例如,第八季第五集: S08E05

  1. 特别篇或者OVA目录文件
    季命名以S00开始

好的文件命名是决定刮削是否准确的关键

效果如图

assets/nas018.png

1.9 番号整理

1.9.1 软件下载安装

使用MDXC进行番号刮削

项目地址:
https://github.com/sqzw-x/mdcx

下载地址:
https://github.com/sqzw-x/mdcx/releases/tag/120240321

配置如下:

assets/nas013.png

assets/nas014.png

assets/nas015.png

assets/nas016.png

具体不能多说了,自己研究吧。展示下刮削效果

assets/nas021.png

assets/nas022.png

1.9.2 番号命名规则

刮削前文件名称整理

  1. 目录名称不动
  2. 文件名称修改为番号名称

刮削重命名配置

  1. 目录名称不动
  2. 文件名称修改为番号名称

音乐刮削

下载Music Tag音乐标签客户端
(或者Mp3Tag,需要自己配置源)

文件-添加目录:添加音乐目录

工具栏-点击自动匹配标签:自动匹配项,除了标题和艺术家都选上

文件-重命名:按照 (艺术家 - 标题) 命名

1.9 番号整理

1.9.1 软件下载安装

使用MDXC进行番号刮削

项目地址:
https://github.com/sqzw-x/mdcx

下载地址:
https://github.com/sqzw-x/mdcx/releases/tag/120240321

配置如下:

assets/nas013.png

assets/nas014.png

assets/nas015.png

assets/nas016.png

具体不能多说了,自己研究吧。展示下刮削效果

assets/nas021.png

assets/nas022.png

1.9.2 番号命名规则

刮削前文件名称整理

  1. 目录名称不动
  2. 文件名称修改为番号名称

刮削重命名配置

  1. 目录名称不动
  2. 文件名称修改为番号名称

1 方案说明

硬件搭配方便,主要做出了一下三种硬件方案。

  1. 省电方案
硬件 型号 价格
CPU 8100 180
主板 华南B250 250
内存 玖合DDR4-8GB X2 200
m2固态 铠侠SD10-1T 480
电源 ds-atx 100
电源 150dc电源 100
机箱 盘隆-4盘位机箱 260

8100带T和不带T,待机情况下功耗没有区别。
待机功耗。以上13W。

2 硬盘柜版

说明。采用小主机+usb+外接硬盘柜方案。

硬件 型号 价格
CPU 七喜N100准系统小主机 700
内存 玖合dd4笔记本内存3200-16G 180
m2固态 铠侠SD10-1T 480
硬盘柜 奥睿科5盘位硬盘柜-无RAID 800
总计 2160元

待机功耗。以上20W。

VGA图形模式

include/stdint.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef __LIB_STDINT_H
#define __LIB_STDINT_H

typedef signed char int8;
typedef signed short int16;
typedef signed int int32;
typedef signed long long int64;

typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint32;
typedef unsigned int uint;
typedef unsigned long long uint64;

#endif

include/console.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

#ifndef __LIB_CONSOLE_H
#define __LIB_CONSOLE_H

#include "stdint.h"

uint16 get_cursor();

void set_cursor(uint16 pos);

void put_char(char ch);

void put_string(char* chs);

void put_pointer(uint32 addr);

#endif

lib/console.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

#include "../include/stdint.h"
#include "../include/io.h"
#include "../include/console.h"

#define VGA_BASE 0xB8000
#define DATA_BASE 0x60000
#define ROW 25
#define COL 80

uint16 get_cursor()
{
//get high cursor value
io_out8(0x3d4, 0x0e);
uint8 cursor_high = io_in8(0x3d5);
//get low cursor value
io_out8(0x3d4, 0x0f);
uint8 cursor_low = io_in8(0x3d5);
//high + low
return (cursor_high << 8) + (cursor_low & 0xff);
}

void set_cursor(uint16 pos)
{
uint8 cursor_high = pos >> 8;
uint8 cursor_low = pos & 0xff;
//set high cursor value
io_out8(0x3d4, 0x0e);
io_out8(0x3d5, cursor_high);
//set low cursor value
io_out8(0x3d4, 0x0f);
io_out8(0x3d5, cursor_low);
}

void put_char(char ch)
{

uint16 pos = get_cursor();
char *pvga = (char *)VGA_BASE;

//字符
switch (ch)
{
case 0x0d: //RETURN
pos = (pos / COL) * COL;
break;
case 0x0a: //NEW LINE
pos = pos + COL;
pos = (pos / COL) * COL;
break;
case 0x09: //TAB
pos=pos+4;
break;
case 0x08: //BACKSPACE
pos--;
*(pvga + pos * 2) = 0x00;
break;
default:
*(pvga + pos * 2) = ch;
pos++;
}
//字符超出时, 滚屏
if (pos + 1 > ROW * COL )
{
int display = (ROW -1) * COL;
pos = pos - COL;
for (int i = 0; i < display; i++)
{
*(pvga + 0x00 + i * 2) = *(pvga + 0xa0 + i * 2);
}
for (int i = 0; i < COL ; i++)
{
*(pvga + display * 2 + i * 2) = 0x0;
}
}
set_cursor(pos);
}

void put_string(char *chs)
{
int i = 0;
while (*(chs + i) != 0)
{
put_char(*(chs + i));
i++;
}
}

void put_pointer(uint32 addr)
{
put_char('0');
put_char('x');
int i = 0;
char *chs = (char *)DATA_BASE; //need point mem alloc
do
{
*(chs + i) = (char)(addr % 10 + '0'); //取下一个数字
i++;

} while ((addr /= 10) > 0); //删除该数字
for (int j = i; j >= 0; j--)
{ //生成的数字是逆序的,所以要逆序输出
put_char(*(chs + j));
*(chs + j) = 0;
}
}

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "../include/io.h"
#include "../include/console.h"

int _start(){
put_char('h');
put_char('e');
put_char('l');
put_char('l');
put_char('o');
put_char(',');
put_char('g');
put_char('l');
put_char('o');
put_char('x');
io_hlt();
}

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# tools
PLATFORM=Linux
NASM=nasm
BOCHS=bochs
BXIMAGE=bximage


# args
boot=boot
asm=asm
lib=lib
kernel=kernel
build=build
ENTRY_POINT = 0x70000
CFLAGS = -m32 -c # --Wall -W fno-builtin -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS = -m elf_i386 -e _start -Ttext $(ENTRY_POINT)

target: prepare $(build)/gloxos.img
@echo "build img completed"

$(build)/gloxos.img: $(build)/boot.bin $(build)/loader.bin $(build)/kernel.bin
$(BXIMAGE) -mode=create -imgmode=flat -hd=16M -q $(build)/gloxos.img
sleep 1
dd if=$(build)/boot.bin of=$(build)/gloxos.img bs=512 count=1 conv=notrunc
dd if=$(build)/loader.bin of=$(build)/gloxos.img bs=512 count=1 seek=1 conv=notrunc
dd if=$(build)/kernel.bin of=$(build)/gloxos.img bs=512 count=25 seek=5 conv=notrunc


$(build)/%.bin: $(boot)/%.asm
$(NASM) -f bin -o $(build)/$*.bin $(boot)/$*.asm

$(build)/kernel.bin: $(build)/kernel.o $(build)/io.o $(build)/console.o
$(LD) -o $@ $^ $(LDFLAGS)

$(build)/kernel.o:
$(CC) -o $(build)/kernel.o $(kernel)/main.c -I include $(CFLAGS)

$(build)/console.o:
$(CC) -o $(build)/console.o $(lib)/console.c -I include $(CFLAGS)

$(build)/io.o:
$(NASM) -f elf -o $(build)/io.o $(asm)/io.asm


prepare:
mkdir -p $(build)

clean:
@echo "clean dir $(build)"
rm -rf $(build)/*

platform:
@echo $(PLATFORM)


附:ASCII字符编码表

0x01 SOH(start of headline) 标题开始
0x00 NUL 空字符
0x08 BS (backspace) 退格
0x09 HT (horizontal tab) 水平制表符
0x0A LF (NL line feed, new line) 换行键
0x0B VT (vertical tab) 垂直制表符
0x0C FF (NP form feed, new page) 换页键
0x0D CR (carriage return) 回车键
0x20 (space) 空格
0x7F DEL (delete) 删除

C语言调用汇编

C语言头文件

include/io.h

1
2
3
4
5
6
#ifndef __LIB_IO_H
#define __LIB_IO_H

void io_hlt();

#endif

汇编实现

asm/io.asm

1
2
3
4
5
6
7
8
9
10
11
;io.asm

[bits 32]
global io_hlt


[section .text]
io_hlt: ; void io_hlt
hlt
ret

代码调用

main.c

1
2
3
4
5
6
7
8
9
10
11
#include "include/io.h"

typedef unsigned char int8;

int _start(){
int8 *pvga = (int8 *)0xa0000; //填充到显示内存的初始地址
for(int i = 0;i <= 0xffff;i++){
*(pvga + i) = i & 0x0F; //显卡内存存填充颜色值
}
io_hlt();
}

VGA显示模式

VGA图形模式

如果想要改变显示方式,你可以通过BIOS中断来设置显示模式。

参考BIOS中断大全,随便列出几个

1. 设置VGA显示模式

中断操作:INT 0x10,AH=00H

寄存器 说明
AH 操作模式:设置显示模式 00H
AL 显示模式

AL = 显示模式值
见下表所示

寄存器值 分辨率 颜色域 显示模式
00H 40×25 16色 文本
01H 40×25 16色 文本
02H 80×25 16色 文本
03H 80×25 16色 文本
11H 640×480 2色 图形
12H 640×480 16色 图形
13H 320x200 256色 图形

设置显示模式

接下来一步我们将设置从文本模式,变为图形显示模式

核心代码:

1
2
3
4
5
6
;----------------------------
;设置显示模式 320x200x256色
;----------------------------
mov ah, 0x0 ;VGA显示模式
mov al, 0x13 ;320x200x256色
int 0x10

图形模式下的内存映射

内存地址 空间大小 用途
0xA0000 ~ 0xAFFFF 64KB 彩色模式显示内容
0xB0000 ~ 0xB7FFF 32KB 黑白显示内容
0xB8000 ~ 0xBFFFF 32KB 文本模式显示内容
0xC0000 ~ 0xC7FFF 32KB 显示器适配BIOS
0xC8000 ~ 0xEFFFF 160KB 硬件适配器ROM
0xF0000 ~ 0xFFFFF 64KB BIOS程序。BOIS入口地址为: 0xF0000-0xFFFFF

所有在图形模式下。修改0xa0000 - 0xaffff则可以改编显示内容

1
2
3
4
5
6
7
8
9
10
typedef unsigned char int8;

int _start(){
int8 *pvga = (int8 *)0xa0000; //填充到显示内存的初始地址
for(int i = 0;i <= 0xffff;i++){
*(pvga + i) = i & 0x0F; //显卡内存存填充颜色值
}
fin:
goto fin;
}

或者

0xe0000000-0xe00140000

1
2
3
4
5
6
;----------------------------
;设置显示模式 1280×1024*256色
;----------------------------
mov ax, 0x4F02 ;超级VGA显示器模式
mov bx, 0x107
int 0x10

仿照30天里面,制作彩条图形输出

1
2
3
4
5
6
7
8
9
10
typedef unsigned char int8;

int _start(){
int8 *pvga = (int8 *)0xe0000000; //填充到显示内存的初始地址
for(int i = 0;i <= 0x140000;i++){
*(pvga + i) = i & 0x0F; //显卡内存存填充颜色值
}
fin:
goto fin;
}

·

加载执行c语言程序的代码并执行

images/4_3_1.png

ELF文件

上次说过了,其实目标文件和可执行文件都是ELF格式文件

ELF索引表

ELF 文件包括三个索引表

  • ELF Header: ELF文件头

    作用:

    1. 指定程序入口

    2. 定位Program  header  table位置

    3. 定位Section  header  table位置

  • Program  header  table:程序头表。

    作用:

    1)查询segment的位置(一个segment可能会包含多个Section)

    2)根据此表创建内存中创建映像

  • Section  header  table:节区头表。

    作用:

    1)存储文件节区的信息

    2)根据此表定位代码段,数据段位置

ELF内容区

  • 字符串表:interp,.strtable ,.shstrtab,.dynstr节区
  • 符号表:.symtab
  • 代码段:.text节区
  • 数据段:.data、.rodata 、.bss节区
  • 全局偏移表:.got节区
  • 过程链接表: .plt节区
  • 哈希表:指.hash节区
  • 编译器版信息:.comment

ELF字段类型

  • Elf32_Addr:4字节,无符号程序地址

  • Elf32_Half: 2字节,无符号中等整数

  • Elf32_Off:4 字节,无符号文件偏移

  • Elf32_SWord:4 字节,有符号大整数

  • Elf32_Word :4字节,无符号大整数

ELF的三种Header格式

ELF Header格式

字段 类型 长度 说明
ident char [16] 16 魔数
type Elf32_Half 2 文件类型
machine Elf32_Half 2 硬件平台
version Elf32_Word 4 版本
entry Elf32_Addr 4 程序进入点 <24>
phoff Elf32_Off 4 程序头表偏移量<28>
shoff Elf32_Off 4 节头表偏移量
flags Elf32_Word 4 处理器特定标志
ehsize Elf32_Half 2 ELF头部大小
phentsize Elf32_Half 2 程序头大小<42>
phnum Elf32_Half 2 程序头数量<44>
shentsize Elf32_Half 2 节头大小
shnum Elf32_Half 2 节头数量
shstrndx Elf32_Half 2 字符串表索引节头

ELF文件分析

查看文件头

readelf -h loader1.bin

images/5_3_1.png

Program Header 程序头格式

字段 类型 长度 说明
type Elf32_Word 4 段类型
offset Elf32_Off 4 偏移量 <4>
vaddr Elf32_Addr 4 内存虚拟地址 <8>
paddr Elf32_Addr 4 物理地址
filesz Elf32_Word 4 段大小(文件占用)<16>
memsz Elf32_Word 4 段大小(内存占用)
flag Elf32_Word 4 段标志
align Elf32_Word 4 段对齐

查看程序头:

$ readelf -l 文件名

images/4_1_1.png

Section Header节头

节头格式

字段 类型 长度 说明
name Elf32_Word 4 节名称
type Elf32_Word 4 节类型
flags Elf32_Word 4 节属性
addr Elf32_Addr 4 节区地址
offset Elf32_Off 2 偏移量
size Elf32_Word 4 节大小(文件)
link Elf32_Word 4 节区头部表索引链接
info Elf32_Word 4 附加信息
addralign Elf32_Word 4 地址对齐
entsize Elf32_Word 4 项目表长度

查看字段头:

$ readelf -S 文件名

images/4_1_2.png

符号表 Symbol table

其他

  • 查看全部信息

    1
    readelf -a
  • 查看所有分段大小

size loader0.o

images/2.7_4.png

  • 查看分段内容

objdump -s loader0.o

images/2.7_5.png

  • 查看符号表

objdump -t loader0.o

images/2.7_2.png

  • 反编译

    objdump -S loader0.o

解析ELF文件

准备工作

输出心形的c语言代码

kernel/main.c

1
2
3
4
5
6
7
8
9
10
11
12
typedef unsigned char int8;

int _start(){
int8 *pvga = (int8 *)0xb8000; //填充到显示内存的初始地址
for(int i = 0;i <= 0xffff;){
//char: 0x3 ,color: 0x104
*(pvga + i) = (int8)0x03;i++; //符号:心形
*(pvga + i)= (int8)0x14;i++; //颜色:背景蓝前景红
}
fin:
goto fin;
}

内存复制

数据传送指令

movsb即字符串传送指令,这条指令按字节送数据。

参数:

esi:数据源地址
edi:数据目标地址

默认复制一个字节

rep movsb : 复制多个字节

参数:

  • esi:数据源地址

  • edi:数据目标地址

  • ecx:复制字节数

1
2
3
4
5
6
7
8
9
10
;------------------    
;内存复制 : 源地址,目标地址,字节数
;入参:
; esi = 源地址
; edi = 目标地址
; ecx = 字节数
MemCopy:
rep movsb;
ret

解析执行ELF文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

;-----------------------------------
; 解析执行ELF文件: AnalyzeELF
; 入参:
; eax=文件内存位置
; 出参:
; ebx=入口地址
AnalyzeELF:


mov edx, 0
mov ecx, 0
mov ebx, [eax + 28] ;program header偏移量
add ebx, eax ;program header位置
mov dx, [eax + 42] ;program header大小
mov cx, [eax + 44] ;program header数量


.loopSegment:
cmp byte [ebx + 0],0 ;ptype为0,程序段未使用
je .nextSegment


push ecx;
mov ecx, 0;
;---------------
;复制segment
mov esi, [ebx + 4] ;segment偏移量
add esi, eax ;src
mov edi, [ebx + 8] ;dist
mov cx, [ebx + 16] ;len
call MemCopy
pop ecx;

.nextSegment:
add ebx, edx
loop .loopSegment ;继续读取下一个segment
mov ebx, [eax + 24] ;返回入口地址
ret


;------------------
;内存复制 : 源地址,目标地址,字节数
;入参:
; esi = 源地址
; edi = 目标地址
; ecx = 字节数
MemCopy:
rep movsb;
ret

调用处的代码

1
2
3
4
5
6
7
;----------------------
;解析并执行ELF文件
AnalyzeKernel:
mov eax, KERNEL_BASE_ADDR
call AnalyzeELF
jmp ebx

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# tools
PLATFORM=Linux
NASM=nasm
BOCHS=bochs
BXIMAGE=bximage


# args
boot=boot
kernel=kernel
build=build
ENTRY_POINT = 0x70000
CFLAGS = -m32 -c # --Wall -W fno-builtin -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS = -m elf_i386 -e _start -Ttext $(ENTRY_POINT)

target: prepare $(build)/gloxos.img
@echo "build img completed"

$(build)/gloxos.img: $(build)/boot.bin $(build)/loader.bin $(build)/kernel.bin
$(BXIMAGE) -mode=create -imgmode=flat -hd=16M -q $(build)/gloxos.img
sleep 2
dd if=$(build)/boot.bin of=$(build)/gloxos.img bs=512 count=1 conv=notrunc
dd if=$(build)/loader.bin of=$(build)/gloxos.img bs=512 count=1 seek=1 conv=notrunc
dd if=$(build)/kernel.bin of=$(build)/gloxos.img bs=512 count=25 seek=5 conv=notrunc


$(build)/kernel.bin: $(build)/kernel.o
$(LD) $(build)/main.o -o $(build)/kernel.bin $(LDFLAGS)

$(build)/kernel.o: $(kernel)/main.c
$(CC) $(kernel)/main.c -o $(build)/main.o $(CFLAGS)

$(build)/%.bin: $(boot)/%.asm
$(NASM) -f bin -o $(build)/$*.bin $(boot)/$*.asm

prepare:
@echo "prepare dir $(build)"
ifeq ($(build), $(wildcard $(build)))
@echo "build directory exist..."
else
mkdir -p $(build)
endif

clean:
@echo "clean dir $(build)"
rm -rf $(build)/*

platform:
@echo $(PLATFORM)

加载执行c语言程序的代码并执行

images/4_2_1.png

天空任鸟飞,海阔凭鱼跃。我们已经成功的从启动,到进入c语言的世界,接下来就是无限的可能性。

加载器

引导的第三阶段

  1. 第一次加载文件,引导扇区,系统的入口
  2. 第二次加载文件,loader文件,负责从16位实模式进入32位保护模式
  3. 第三次加载文件,进入C语言实现,运行在32位系统下。

按照正常的逻辑,BIOS载入执行引导扇区后,主引导记录就应该去查询并执行内核文件了。

但是存在着下面几个问题:

  1. 没有文件系统,就算磁盘有内核的内容,引导代码也不知道应该从哪里加载内核内容执行。

  2. 主引导记录只有512KB,没有办法做更多的操作

因此需要二阶段的引导,流程如下:

主引导记录继续加载执行一段第二阶段引导程序,而这段内容可能占据几个扇区。

第二阶引导程序会构建文件系统,然后根据文件系统查找和执行内核。

第二阶引导程序我们称之为loader。加载器。

读取加载器

磁盘的1扇区为mbr扇区 ,我们规定其后面2-6扇区为loader内容的扇区。流程如下:

BIOS加载执行引导扇区。

引导扇区访问磁盘,并读取磁盘的2-6扇区。

复制磁盘的2-6扇区到内存的0x90000位置,并执行。

1. loader文件的位置

1)内存分配

在boot引导完成后,当前系统的内存分配如下:

内存 0x7c00-0x7dff :引导扇区

内存 0x8000-N : 读取的磁盘内容 ( 其中的 0xc200-0xc3ff 是loader文件)

2)loader在内存的位置

在前面的引导程序里面已经将磁盘的10个柱面加载到了内存单元的0x8000处。而在ima文件中程序的起始位置(也就是引导扇区)在磁盘中的位置是 0x4200。

同样在内存也是从 0x8000 往后的 0x4200 位置。

所以loader的内容在内存单元的开始位置为:0x8000+0x4200=0xc200位置。

说明:

整个磁盘文件包括引导扇区512+磁盘文件sys。loader在sys文件的0x4200位置,引导扇区在此之后。

因此磁盘文件复制到内存0x8000处,所以得出loader的位置需要0x8000+0x4200所以得出0xc200这个位置。

但是其实0x4200之前是没什么用的。因此将磁盘引导区原本在磁盘的位置靠后现在给提前了,复制到了8000-81ff的位置,单独放了一块。

这个和真实磁盘文件放置不同(前后问题),慢慢理解。

loader1.asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
;RatsOS
;TAB=4

%include "boot/boot.inc"

[bits 32]

org 0x9800

jmp Entry

;程序核心内容
Entry:
;显存段地址
mov byte [gs:0x00],'h' ;输出字符
mov byte [gs:0x01],0x17 ;设置颜色(背景色蓝,前景色白)
mov byte [gs:0x02],'e'
mov byte [gs:0x03],0x17
mov byte [gs:0x04],'l'
mov byte [gs:0x05],0x17
mov byte [gs:0x06],'l'
mov byte [gs:0x07],0x17
mov byte [gs:0x08],'o'
mov byte [gs:0x09],0x17
mov byte [gs:0x0a],','
mov byte [gs:0x0b],0x17
mov byte [gs:0x0c],'l'
mov byte [gs:0x0d],0x17
mov byte [gs:0x0e],'o'
mov byte [gs:0x0f],0x17
mov byte [gs:0x10],'a'
mov byte [gs:0x11],0x17
mov byte [gs:0x12],'d'
mov byte [gs:0x13],0x17
mov byte [gs:0x14],'e'
mov byte [gs:0x15],0x17
mov byte [gs:0x16],'r'
mov byte [gs:0x17],0x17
mov byte [gs:0x18],'1'
mov byte [gs:0x19],0x17


jmp $ ;让CPU挂起,等待指令。

内存分配

磁盘分布图

磁盘区域 数据内容 说明
0x0000 - 0x0200 (1个扇区) boot.bin 引导扇区
0x0200 - 0x0A00(4个扇区) loader.bin
0x0A00 - 0x1400(25个扇区) head.bin

实模式内存分布图

内存区域 大小 数据内容 说明
0x0000 - 0x03FF 1K 中断向量表
0x0400 - 0x04FF 256B BIOS数据区
0x7C00 - 0x7DFF 512B boot.bin 引导扇区的内存地址
0x9200 - 0x99FF 2048B loader.bin
0x9A00 - 0xc200 10240B loader1.bin
0x70000 - 0x709FF loader1.bin映像
0x90000 - 0x92FFF 系统参数信息表
0x9FC00 - 0x9FFFF 1K 扩展BISO数据区域
0xB8000 - 0xB8FFFF 显存地址(默认)
0xA00000 - 0xAFFFFF 显存地址64KB(图形模式)
  • 一般来说,0x00000-0x9FFFF(576KB)这段物理地址空间是提供给内存的。
  • 而 0xA0000-0xEFFFF 这段物理地址空间(320KB)则是留给外围设备的,包括显卡(显卡占用0xB8000-0xC7FFF)。
  • 而 0xB0000-0xBFFFF 这段物理地址空间(64KB)则是由主板的ROM-BIOS芯片提供。

保护模式

内存区域 大小 数据内容 说明
0x1000 - 0x17FF 2048B 系统信息
0x9000 - 0x97FF 2048B loader.bin loader内存位置
0x9800 - 0xBFFF 10KB kernel.bin kernel内存位置
0xB8000 - 0xB8FFFF 显存地址(默认)

分页模式

内存区域 大小 数据内容 说明
0x0000 - 0xffff loader内存位置
0xffff - 0xBFFF GDT表 kernel内存位置

## GDT信息

GDT 段类型 基本地址 限制 属性
GDT[0x00] Data segment base=0x00000000 limit=0xffffffff Read/Write
GDT[0x01] Code segment base=0x00280000 limit=0x0007ffff Execute/Read

系统参数信息表

|内存地址| 字节偏移量 | 说明 | | |
|— |— |— |—|—|
|0x90000| 2|内存ards个数|—|
|0x92000| 2|内存ards个数|—|
|0x92002| 200|ards表|—|

C语言程序

工具

首先,需要的工具软件列表:

  • gcc编译器:

编译C语言程序

1. 为什么没有main函数

main函数链接时需要一些系统库文件。而我们的系统目前并没有任何的系统库可以用,会导致报错。

所以此处不能使用main函数。

那么我们使用默认的入口_start符号(不设置,则默认为0)

kernel/main.c

1
2
3
4
5
6
7
8
9
10
11
typedef char * Pointer;
int _start(){
Pointer pvga = (Pointer)0xb8000; //填充到显示内存的初始地址
for(int i = 0;i <= 0xffff;){
//char: 0x3 ,color: 0x104
*(pvga + i) = 0x03;i++; //显卡内存存填充颜色值,红色心形
*(pvga + i)= 0x104;i++;
}
fin:
goto fin;
}

编译成目标文件

$ gcc bootloader1.c -m32 -c -nostdinc -Iinclude

-o build/loader1.o

链接C语言程序

1. 为什么要指定程序入口
由于在保护模式下,我们默认加载到 0x00001000处执行代码。所以,下载需要做的是
1)指定当前c语言程序的入口地址为0x00001000
2)复制程序段的执行程序段到0x00001000

链接并指定程序入口

ld -m elf_i386 -s -Ttext 0x00001000 build/loader1 -o loader1.bin

使用

查看文件信息

file loader1.bin

查看纯二进制内容

xxd loader1.bin

查看反编译内容

objdump -S loade1.bin

查看文件信息

readelf -e loader1.bin

GDT全局描述符表

什么是GDT全局描述符表

GDT全称为Global Descriptor Table,全局描述符表。

保护模式的寻址方式不在使用寄存器分段的方式直接寻址方式了。而采用的是使用GDT(全局分段描述表)来寻址。从而使用更多的内存地址。

创建GDT全局描述符表使用到一个48位的寄存器:GDTR寄存器。

1)首先,在内存中划分一些内存段,并且每个内存段赋予一个索引。

2)然后,使用lgdt指令,设置GDT的索引和表信息的内存地址到GDTR寄存器。

3)进入保护模式,指令跳转,从实模式分段方式寻址切换到使用GDT分段方式寻址。

  1. GDT可以被放在内存的任何地方,只要提供内存地址给GDTR寄存器就可以了。

GDT格式

GDT全局描述符表

  • 表基地址,表基地址位GDT段表在内存的地址,GDT段表是一个列表,存储了多个 GDT段描述符。
  • 表界限:GDT段表的空间信息,以字节为单位。

2_1_1.png

GDT全局描述符表 = GDT段表基地址 | 16位表界限

GDT段表 =  GDT段描述符 |  GDT段描述符 | GDT段描述符 …

表界限 = GDT字节数 - 1 (表示 0 - 0x…)

GDT段描述符

images/003.png

GDT段描述符,用来描述在GDT方式在内存中分配的一个段信息,总共8字节64位。

GDT段描述符结构

为了兼容以前的CPU,GDT段描述符的信息被分割成几个部分,格式如下:

GDT段描述符 = 

段基址 (8位)| 段描述符(4位) | 段界限(4位) | 段描述符(8位) 段基址 (8位)

段基址 (16位) | 段界限(16位)

段描述符定义

  • 段基址:规定段的起始地址,长度32位.
  • 段界限:规定段的大小,长度20位。段界限可以是以4KB或者1B为单元大小
  • 段属性:确定段的各种性质.长度(12位)

段属性:

  • G 粒度位: 段界限的单位大小,G=1表示段界限以4KB为单元单位,G=0表示段界限以1B为单元单位
  • D/B 表示操作数为多少位, 0表示16位操作数,1表示32位操作数
  • L : 0 表示非64位代码段,1表示64位代码段
  • AVL :可用字段,暂时没什么用
  • P 段存在位:通常为1,表示段存在于内存中,0则此段为非法的,不能被用来实现地址转换
  • DPL 特权级(2位): 用来实现保护机制
  • S 为0表示系统段,为1表示非系统段
  • type 类型(4位): 用于区别不同类型的描述符。内存段或者门的子类型

type值

Type位 说明 取值
代码段时
X:3位 代码段值为1 0:为数据段
1:为代码段
C:2位 访问位 0:为普通段
1:为一致码段
R:1位 是否可读 0:只执行
1:可读
A:0位 访问位. 该段是否被访问过 0 :未访问
1:已访问
数据段时
X:3位 数据段值为1 0:为数据段
1:为代码段
E:2位 扩展方向 0:向高位扩展
1:向低位扩展
W:1位 是否可写 0:只读
1:可写
A:0位 访问位 0: 未访问
1:已访问

段界限:

段界限边界值 = (描述符的段界限值 + 1) × (段界限颗粒读:4Kb 或者 1b) -1

反之:
描述符的段界限值 = (段界限边界值 + 1) /(段界限颗粒读:4Kb 或者 1b)

例如:

16MB的段界限值 = 0x1000000 /(段界限颗粒读:4Kb 或者 1b - 1)= 0x0fff

段选择子

段选择子包括三部分:描述符索引(index)、TI、请求特权级(RPL)

GDTR寄存器

在内存中建立完成GDT信息后,CPU会将GDT的内存地址 和 段界限 数据加载入GDTR寄存器

GDTR寄存器数据(48位):

GDTR定义数据(48位) = GDT全局描述符表的大小(16位) + GDT全局描述符表的地址(32位)

lgdt指令

lgdt GDTR定义数据

其中GDT全局描述符表数据格式如下

GDT全局描述符表 = GDT段描述符(64位) | GDT段描述符(64位) | GDT段描述符(64位) …

GDT段描述符 = 段基址 (8位)| 段描述符(4位) | 段界限(4位) | 段描述符(8位) | 段基址 (8位) | 段基址 (16位) | 段界限(16位)

其中,第一个GDT段的数据为空。

GDT临时分段

GDT临时段说明

现在已经进入了保护模式, 目前的改变

  • 可以访问1M以上的内存了
  • 可以使用32位的指令

问题:

由于以前的是实式下段寄存器寻址方式无法使用了,我们必须切换到使用GDT段方式来寻址

首要的任务就是先建立一个临时的GDT段,以便我们接下来的指令操作

目前准备建立3个段,如下:

Base, Limit, Attr

代码段:0x00000000, 0xfffff, 1100_1001_1010B = db 0x0000ffff, 0x00cf9a00

数据段:0x00000000, 0xfffff, 1100_1001_0010B = db 0x0000ffff, 0x00cf9200

vga显卡内存数据段:x000b8000, 0x07fff, 1100_1001_0010B

GDT解析宏

首先创建一个nasm宏,可以进行GDT解析

参数为GDT的Base, Limit, Attr,也就是段基址(32位),段界限(20位),段描述符(12位),然后生成GDT在内存中的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
;---------------------------------------------------------
; 描述符
; usage: Gdt_Descriptor Base, Limit, Attr : 段基址(32位),段界限(20位),段描述符(12位)
; Base: dd
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
;---------------------------------------------------------
%macro Gdt_Descriptor 3
dw %2 & 0xFFFF
dw %1 & 0xFFFF
db (%1 >> 16) & 0xFF
db %3 & 0xFF
db ((%3 >> 4 ) & 0xF0 ) | ((%2 >> 16) & 0x0F )
db (%1 >> 24) & 0xFF
%endmacro

GDT 描述符属性定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
;--------------   gdt描述符属性  -------------
DESC_G_4K equ 1000_0000_0000b
DESC_D_32 equ 0100_0000_0000b
DESC_L equ 0000_0000_0000b ; 64位代码标记,此处标记为0便可。
DESC_AVL equ 0000_0000_0000b ; cpu不用此位,暂置为0
DESC_P equ 0000_1000_0000b
DESC_DPL_0 equ 000_0000b
DESC_DPL_1 equ 010_0000b
DESC_DPL_2 equ 100_0000b
DESC_DPL_3 equ 110_0000b
DESC_S_CODE equ 1_0000b
DESC_S_DATA equ 1_0000b
DESC_S_SYS equ 0_0000b
DESC_TYPE_CODE equ 1010b ;x=1可执行代码段,c=0普通,r=1可读,a=0已访问位a清0
DESC_TYPE_DATA equ 0010b ;x=0数据段,e=0向高位扩展,w=1可写,a=0已访问位a清0.

;-------------- 选择子属性 ---------------
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b


DESC_CODE equ DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_P + DESC_DPL0 + DESC_S_CODE + DESC_TYPE_CODE
DESC_DATA equ DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_P + DESC_DPL0 + DESC_S_DATA + DESC_TYPE_DATA

定义GDT全局描述符表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
;---------------------------------
;定义GDT全局描述符表
;code: 0x0000ffff, 0x00cf9a00
;data: 0x0000ffff, 0x00cf9200
;vga:
Gdt_Addr:
dw 8*4-1 ;指定段上限为4(GDT全局描述符表的大小)
dd gdt_table_addr ;GDT全局描述符表的地址
Gdt_Table_Addr:
Gdt_Descriptor 0,0,0
Label_Sel_Code: Gdt_Descriptor 0x00000000, 0xfffff, DESC_CODE ;可以执行的段
Label_Sel_Data: Gdt_Descriptor 0x00000000, 0xfffff, DESC_DATA ;可以读写的段
Label_Sel_VGA: Gdt_Descriptor 0x000b8000, 0x07fff, DESC_DATA ;vga段
dw 0

;--------------------------------
;选择子
Selector_Code equ Label_Sel_Code - Gdt_Table_Addr
Selector_Data equ Label_Sel_Data - Gdt_Table_Addr
Selector_VGA equ Label_Sel_VGA - Gdt_Table_Addr

加载gdt

1
2
3
;---------------------------
;加载GDT
lgdt [Gdt_Addr]

代码

创建常量头文件

创建 boot.inc文件。用来配置常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
; boot.inc
;---------------------------------------------------------
; 描述符
; usage: Gdt_Descriptor Base, Limit, Attr : 段基址(32位),段界限(20位),段描述符(12位)
; Base: dd
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
;---------------------------------------------------------
%macro Gdt_Descriptor 3
dw %2 & 0xFFFF
dw %1 & 0xFFFF
db (%1 >> 16) & 0xFF
db %3 & 0xFF
db ((%3 >> 4 ) & 0xF0 ) | ((%2 >> 16) & 0x0F )
db (%1 >> 24) & 0xFF
%endmacro



;-------------- gdt描述符属性 -------------
DESC_G_4K equ 1000_0000_0000b
DESC_D_32 equ 0100_0000_0000b
DESC_L equ 0000_0000_0000b ; 64位代码标记,此处标记为0便可。
DESC_AVL equ 0000_0000_0000b ; cpu不用此位,暂置为0
DESC_P equ 0000_1000_0000b
DESC_DPL_0 equ 000_0000b
DESC_DPL_1 equ 010_0000b
DESC_DPL_2 equ 100_0000b
DESC_DPL_3 equ 110_0000b
DESC_S_CODE equ 1_0000b
DESC_S_DATA equ 1_0000b
DESC_S_SYS equ 0_0000b
DESC_TYPE_CODE equ 1010b ;x=1可执行代码段,c=0普通,r=1可读,a=0已访问位a清0
DESC_TYPE_DATA equ 0010b ;x=0数据段,e=0向高位扩展,w=1可写,a=0已访问位a清0.


;-------------- 选择子属性 ---------------
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b

DESC_CODE equ DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE
DESC_DATA equ DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA

;----------- loader const ------------------
LOADER_SECTOR_LBA equ 0x1 ;第2个逻辑扇区开始
LOADER_SECTOR_NUM equ 9 ;读取9个扇区
LOADER_BASE_ADDR equ 0x9000 ;内存地址0x9200
LOADER1_BASE_ADDR equ 0x9800 ;内存地址0x9200
;-------------------------------------------

loader.asm文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
;GloxOS LOADER [0x9000]
;Tab=4
[bits 16]

%include "boot/boot.inc"

section loader vstart=LOADER_BASE_ADDR ;指明程序的偏移的基地址

jmp Entry;



;程序核心内容
Entry:

;----------------------
;禁止CPU级别的中断,进入保护模式时没有建立中断表
;----------------------
cli

;----------------------
;打开A20
;----------------------
in al,0x92
or al,0000_0010B ;设置第1位为1
out 0x92,al

;----------------------
;加载GDT
;----------------------
lgdt [Gdt_Addr]

;----------------------
;进入保护模式
;----------------------
mov eax,cr0
or eax,0x1 ;设置第0位为1
mov cr0,eax

;程序挂起
Fin:
hlt ;让CPU挂起,等待指令。
jmp Fin

;---------------------------------
;定义GDT全局描述符表
;code: 0x0000ffff, 0x00cf9a00
;data: 0x0000ffff, 0x00cf9200
;vga:
;---------------------------------
Gdt_Addr:
dw 8*4-1 ;指定段上限为4(GDT全局描述符表的大小)
dd Gdt_Table_Addr ;GDT全局描述符表的地址
Gdt_Table_Addr:
Gdt_Descriptor 0,0,0
Gdt_Descriptor 0x00000000, 0xfffff, DESC_CODE ;可以执行的段
Gdt_Descriptor 0x00000000, 0xfffff, DESC_DATA ;可以读写的段
Gdt_Descriptor 0x000b8000, 0x07fff, DESC_DATA ;vga段
dw 0

times 512-($-$$) db 0 ; 处理当前行$至结束(1FE)的填充

测试

使用bochs执行

打好断点后,执行并查看gtd描述符数据是否正确。

info gdt

2_1_1.png

0%